/*
* Copyright (C) Microsoft. All rights reserved.  
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not 
* use this file except in compliance with the License.  You may obtain a copy 
* of the License at http://www.apache.org/licenses/LICENSE-2.0  
* 
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED 
* WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
* MERCHANTABLITY OR NON-INFRINGEMENT. 
*
* See the Apache License, Version 2.0 for specific language governing 
* permissions and limitations under the License.
*
*/
#pragma once

#include <baja/graphics/dx11.1/details/common.hpp>
#include <baja/graphics/dx11.1/graphics_fwd.hpp>
#include <baja/graphics/dx11.1/details/enum_mappings.hpp>
#include <baja/win32/details/com_base.hpp>

#include <baja/graphics/dx11.1/texture_impl.hpp>

#include <baja/math.hpp>

namespace baja { namespace graphics { namespace dx11_1 {

BAJA_BEGIN_CONSTANTS(constants)
    BAJA_DECLARE_CONST(float32, baseDpi, 96.f)
BAJA_END_CONSTANTS

class font : public ifont, public std::enable_shared_from_this<ifont>
{
public:

    font() {}

    static std::shared_ptr<font> create(
        const std::shared_ptr<iengine>& graphics,
        const wchar* fontHeight,
        const uint32 emHeight,
        const bool rightToLeft
        )
    {
        std::shared_ptr<font> f = std::make_shared<font>();
        f->initialize(graphics, fontHeight, emHeight, rightToLeft);
        return f;
    }

    // ifont methods
    
    // measure the bounding rectangle of the specified text
    void measureText(
        const wchar* text,
        const horizontal_alignment horzAlignment,
        const vertical_alignment vertAlignment,
        const bool includeOverhang,
        m::rectangle* rect
        )
    {
        BAJA_CHECK_ARG(text);
        BAJA_CHECK_ARG(rect);

        // update the text layout
        std::wstring textToMeasure = text;
        const_cast<font*>(this)->updateTextLayout(textToMeasure);

        // measure the text
        this->measureTextInternal(
            horzAlignment,
            vertAlignment,
            includeOverhang,
            rect
            );
    }

    // render the specified text with the given properties
    inline void font::renderText(
        const std::shared_ptr<iengine>& graphics,
        const wchar* text,
        const packed_color color,
        const horizontal_alignment horzAlignment,
        const vertical_alignment vertAlignment,
        const m::rectangle* rect
        )
    {
        BAJA_CHECK_ARG(graphics);
        BAJA_CHECK_ARG(text);
        BAJA_CHECK_ARG(rect);

        // update the text layout
        std::wstring textToDraw = text;
        const_cast<font*>(this)->updateTextLayout(textToDraw);

        win32_result wr;

        float32 offsetX;
        float32 offsetY;

        m::rectangle measuredRect;
        this->measureTextInternal(
            horizontal_alignment::left,
            vertical_alignment::top,
            false,
            &measuredRect
            );

        if (horzAlignment == horizontal_alignment::left)
        {
            offsetX = measuredRect.left;
        }
        else if (horzAlignment == horizontal_alignment::center)
        {
            offsetX = (measuredRect.left + measuredRect.right) * 0.5f;
        }
        else
        {
            offsetX = measuredRect.right;
        }

        if (vertAlignment == vertical_alignment::top)
        {
            offsetY = measuredRect.top;
        }
        else if (vertAlignment == vertical_alignment::center)
        {
            offsetY = (measuredRect.top + measuredRect.bottom) * 0.5f;
        }
        else
        {
            offsetY = measuredRect.bottom;
        }
        
        WRL::ComPtr<ID2D1DeviceContext> d2d1DeviceContext = b::bajaInterfaceToCom<iengine, ID2D1DeviceContext>(graphics);
        WRL::ComPtr<ID2D1Brush> brush;
        WRL::ComPtr<ID2D1SolidColorBrush> solidBrush;

        d2d1DeviceContext->CreateSolidColorBrush(
            D2D1::ColorF(
                unpackColor_R(color),
                unpackColor_G(color),
                unpackColor_B(color),
                unpackColor_A(color)
                ),
            &solidBrush
            );

        solidBrush.As(&brush);
            
        WRL::ComPtr<ID2D1DrawingStateBlock> stateBlock = b::bajaInterfaceToCom<iengine, ID2D1DrawingStateBlock>(graphics);
        d2d1DeviceContext->SaveDrawingState(stateBlock.Get());
        d2d1DeviceContext->BeginDraw();
        d2d1DeviceContext->SetTransform(D2D1::Matrix3x2F::Identity());

        d2d1DeviceContext->DrawTextLayout(
            D2D1::Point2F(rect->left - offsetX, rect->top - offsetY),
            _cachedTextLayout.Get(),
            brush.Get()
            );

        d2d1DeviceContext->EndDraw();
        d2d1DeviceContext->RestoreDrawingState(stateBlock.Get());
    }

private:

    // initializes the font
    void initialize( 
        const std::shared_ptr<iengine>& graphics,
        const wchar* fontName,
        const uint32 fontHeight,
        bool rightToLeft
        )
    {
        BAJA_CHECK_ARG(graphics);
        BAJA_CHECK_ARG(fontName);

        win32_result wr;

        std::wstring fontNameToUse = fontName;
        if (fontNameToUse.size() == 0)
        {
            fontNameToUse = std::wstring(L"Calibri");
        }

        uint32 height = fontHeight;
        if (height == 0)
        {
            height = 12;
        }

        // remember right-to-left reading
        _rightToLeft = rightToLeft;

        // create the font
        LOGFONT logFont;
        logFont.lfHeight = height;
        logFont.lfWidth = 0;
        logFont.lfEscapement = 0;
        logFont.lfOrientation = 0;
        logFont.lfWeight = FW_NORMAL;
        logFont.lfItalic = 0;
        logFont.lfUnderline = 0;
        logFont.lfStrikeOut = 0;
        logFont.lfCharSet = DEFAULT_CHARSET;
        logFont.lfOutPrecision = OUT_DEFAULT_PRECIS;
        logFont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
        logFont.lfQuality = ANTIALIASED_QUALITY;
        logFont.lfPitchAndFamily = VARIABLE_PITCH;
        strings::copy<wchar>(fontName, logFont.lfFaceName, static_cast<size_t>(LF_FACESIZE));

        // Create the DirectWrite factory.
        wr = DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), reinterpret_cast<IUnknown**>(_directWriteFactory.GetAddressOf()));

        // Create a GDI interop interface.
        wr = _directWriteFactory->GetGdiInterop(_gdiInterop.GetAddressOf());
        
        // Convert to a DirectWrite font.
        wr = _gdiInterop->CreateFontFromLOGFONT(&logFont, _font.GetAddressOf());

        // create a font face
        wr = _font->CreateFontFace(_fontFace.GetAddressOf());

        // Get the font family.
        WRL::ComPtr<IDWriteFontFamily> fontFamily;
        wr = _font->GetFontFamily(fontFamily.GetAddressOf());

        // find best-fitting family for our locale
        std::wstring familyName;
        std::wstring bestLocale;
        wchar localeName[LOCALE_NAME_MAX_LENGTH] = {0};
        LCIDToLocaleName(1033, localeName, LOCALE_NAME_MAX_LENGTH, 0);

        this->getBestFamilyAndLocaleName(fontFamily.Get(), familyName, bestLocale);

        // get font metrics
        DWRITE_FONT_METRICS metrics = {};
        _fontFace->GetMetrics(&metrics);
        _lineSpacing = (int32)(height * ((metrics.ascent + metrics.descent + metrics.lineGap) / (float32)metrics.designUnitsPerEm));

        // Create a text format using the converted font information.
        wr = _directWriteFactory->CreateTextFormat(
            familyName.c_str(),
            NULL,                        
            _font->GetWeight(),
            _font->GetStyle(),
            _font->GetStretch(),
            (float32)height,
            bestLocale.c_str(),
            _textFormat.GetAddressOf()
            );

        // setup reading direction
        wr = _textFormat->SetReadingDirection(!_rightToLeft ? DWRITE_READING_DIRECTION_LEFT_TO_RIGHT : DWRITE_READING_DIRECTION_RIGHT_TO_LEFT);

        // assume leading alignment, and never use word wrapping...
        wr = _textFormat->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_LEADING);
        wr = _textFormat->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP);

        std::shared_ptr<engine> graphicsObj = std::dynamic_pointer_cast<engine>(graphics);
    }

    // gets the actual font family name and best matching local name for the 
    // incoming font family and the user's current locale settings
    void getBestFamilyAndLocaleName(
        IDWriteFontFamily* fontFamily,
        std::wstring& fontFamilyName,
        std::wstring& bestLocaleName
        )
    {
        const uint32 englishLCID = 1033;

        wchar localeName[LOCALE_NAME_MAX_LENGTH] = {0};
        LCIDToLocaleName(englishLCID, localeName, LOCALE_NAME_MAX_LENGTH, 0);

        win32_result wr;
        WRL::ComPtr<IDWriteLocalizedStrings> familyNames;
        wr = fontFamily->GetFamilyNames(familyNames.GetAddressOf());
    
        uint32 index = 0;
        BOOL exists = FALSE;

        wr = familyNames->FindLocaleName(localeName, &index, &exists);

        if (exists != FALSE)
        {
            bestLocaleName = localeName;
        }
        else
        {
            // last resort, pick first locale
            index = 0;

            uint32 bufferSize = 0;
            wr = familyNames->GetLocaleNameLength(index, &bufferSize);

            bufferSize += 1; // add space for the NULL terminator
            std::vector<wchar> name = std::vector<wchar>();
            name.resize(bufferSize);

            wr = familyNames->GetLocaleName(index, name.data(), bufferSize);

            bestLocaleName = name.data();
        }
        
        uint32 bufferSize = 0;
            
        wr = familyNames->GetStringLength(index, &bufferSize);

        bufferSize += 1; // add space for the NULL terminator
        std::vector<wchar> name = std::vector<wchar>();
        name.resize(bufferSize);
        wr = familyNames->GetString(index, name.data(), bufferSize);
        fontFamilyName = name.data();
    } 

    // measure the text
    void measureTextInternal(
        const horizontal_alignment horzAlignment,
        const vertical_alignment vertAlignment,
        const bool includeOverhang,
        m::rectangle* rect
        ) const
    {
        BAJA_CHECK_ARG(_cachedTextLayout);
        BAJA_CHECK_ARG(rect);

        win32_result wr;

        // get the text's layout metrics
        DWRITE_TEXT_METRICS textMetrics = {0};
        wr = _cachedTextLayout->GetMetrics(&textMetrics);

        // adjust for alignment
        m::rectangle measuredRect;
        
        // determine left/right of rectangle
        if (horzAlignment == horizontal_alignment::left)
        {
            measuredRect.left = 0.0f;
            measuredRect.right = textMetrics.width;
        }
        else if (horzAlignment == horizontal_alignment::center)
        {
            measuredRect.left = -textMetrics.width/2.0f;
            measuredRect.right = textMetrics.width/2.0f;
        }
        else
        {
            measuredRect.left = -textMetrics.width;
            measuredRect.right = 0.0f;
        }

        // determine top/bottom of rectangle
        if (vertAlignment == vertical_alignment::top)
        {
            measuredRect.top = 0.0f;
            measuredRect.bottom = textMetrics.height;
        }
        else if (vertAlignment == vertical_alignment::center)
        {
            measuredRect.top = -textMetrics.height/2.0f;
            measuredRect.bottom = textMetrics.height/2.0f;
        }
        else
        {
            measuredRect.top = -textMetrics.height;
            measuredRect.bottom = 0.0f;
        }

        // include overhang if requested
        if (includeOverhang)
        {
            DWRITE_OVERHANG_METRICS overhangMetrics = {0};
            wr = _cachedTextLayout->GetOverhangMetrics(&overhangMetrics);
        
            // adjust right and bottom overhang values to be relative to right and bottom rect, and not initial layout width/height
            overhangMetrics.right = (overhangMetrics.right + textMetrics.layoutWidth) - textMetrics.width;
            overhangMetrics.bottom = (overhangMetrics.bottom + textMetrics.layoutHeight) - textMetrics.height;

            // only adjust rectangle when overhangs would increase rectangle width or height
            measuredRect.left -= max(overhangMetrics.left, 0.0f);
            measuredRect.right += max(overhangMetrics.right, 0.0f);

            measuredRect.top -= max(overhangMetrics.top, 0.0f);
            measuredRect.bottom += max(overhangMetrics.bottom, 0.0f);
        }

        // set the output
        *rect = measuredRect;
    }

    // updates the cached text and text layout
    inline void font::updateTextLayout(const std::wstring& text)
    {
        if (_cachedTextLayout == NULL || _cachedText.compare(text) != 0)
        {
            // create a text layout
            _cachedTextLayout.Reset(); // TEST: same as release ?
            _cachedText.clear();

            win32_result wr;

            wr = _directWriteFactory->CreateTextLayout(
                text.c_str(),
                static_cast<UINT32>(text.length()),
                _textFormat.Get(),
                1.0f,
                static_cast<FLOAT>(_lineSpacing),
                _cachedTextLayout.GetAddressOf()
                );

            _cachedText = text;
        }
    }


private:

    WRL::ComPtr<IDWriteFactory> _directWriteFactory;
    WRL::ComPtr<IDWriteGdiInterop> _gdiInterop;
    WRL::ComPtr<IDWriteFont> _font;
    WRL::ComPtr<IDWriteFontFace> _fontFace;
    WRL::ComPtr<IDWriteTextFormat> _textFormat;
    WRL::ComPtr<IDWriteTextLayout> _cachedTextLayout;

    std::wstring _fontName;
    std::wstring _cachedText;

    int32 _lineSpacing;

    bool _rightToLeft;
};

}}} // namespace baja::graphics::dx11_1

namespace baja { namespace graphics {

inline std::shared_ptr<ifont> createFont(
    const std::shared_ptr<iengine>& graphics,
    const wchar* fontName,
    const uint32 fontHeight,
    const bool rightToLeft
    )
{
    std::shared_ptr<baja::graphics::dx11_1::font> f = baja::graphics::dx11_1::font::create(graphics, fontName, fontHeight, rightToLeft);
    return std::dynamic_pointer_cast<ifont>(f);
}

}} // namespace baja::graphics